package com.lh0811.forge.dependency.security.core.token.impl;

import com.google.common.collect.Lists;
import com.lh0811.forge.dependency.basic_web.exception.CommonException;
import com.lh0811.forge.dependency.basic_web.response.ResponseStatus;
import com.lh0811.forge.dependency.basic_web.response.ServerResponse;
import com.lh0811.forge.dependency.security.core.model.entity.SecurityAuthority;
import com.lh0811.forge.dependency.security.core.model.properties.SecurityBootProperties;
import com.lh0811.forge.dependency.security.core.token.TokenGenerate;
import com.lh0811.forge.dependency.security.core.token.TokenManager;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;

/**
 * 默认token管理器实现（基于 guavaCache）
 *
 * @author lh0811
 * @date 2021/5/8
 */
@Slf4j
public class DefaultTokenManager implements TokenManager {


    // token生成器
    @Resource
    private TokenGenerate tokenGenerate;

    // 配置文件
    @Resource
    private SecurityBootProperties securityBootProperties;

    // 用户权限容器集合
    private List<SecurityAuthority> securityAuthorityList = Lists.newArrayList();

    public DefaultTokenManager() {
        super();
    }

    @Override
    public String tokenGenerateAndSave(SecurityAuthority securityAuthority) throws Exception {
        // 生成token
        String token = tokenGenerate.generate(securityAuthority);
        securityAuthority.setToken(token);
        // 存储token
        saveToken(token, securityAuthority);
        return token;
    }

    @Override
    public void saveToken(String token, SecurityAuthority securityAuthority) throws Exception {

        // 设置过期时间
        securityAuthority.setOverdueDate(DateUtils.addMinutes(new Date(), Integer.valueOf(securityBootProperties.getExpire())));
        // 如果没有任何一个token
        if (CollectionUtils.isEmpty(securityAuthorityList)) {
            securityAuthorityList.add(0, securityAuthority);
            return;
        }
        // 获取用户所有的token
        List<SecurityAuthority> securityAuthorityListOfUser = securityAuthorityList.stream().filter(ele -> StringUtils.equals(ele.getUserId(), securityAuthority.getUserId())).collect(Collectors.toList());
        // 如果该用户没有任何一个token
        if (CollectionUtils.isEmpty(securityAuthorityListOfUser)) {
            securityAuthorityList.add(0, securityAuthority);
            return;
        }

        // 用户的token按照客户端分组
        Map<String, List<SecurityAuthority>> securityAuthorityListOfUserGroupByClientType = securityAuthorityListOfUser.stream().collect(Collectors.groupingBy(ele -> ele.getClientType()));
        // 判断是否是新的clientType
        if (securityAuthorityListOfUserGroupByClientType.keySet().contains(securityAuthority.getClientType())) {
            // 如果客户端可以保留的token数为0 表示不限制最大数量
            if (securityBootProperties.getAllowUserClientTokenNum().compareTo(0) != 0) {
                // 已经存在的ClientType，获取到这个ClientType下所有的口令
                List<SecurityAuthority> securityAuthorityOfClientType = securityAuthorityListOfUser.stream().filter(ele -> StringUtils.equals(ele.getClientType(), securityAuthority.getClientType())).collect(Collectors.toList());
                // 判断是否达到最大的客户端token数
                Integer clientTypeTokenNum = securityAuthorityOfClientType.size();
                // 如果允许的客户端token数已经达到最大值，需要移除多余的token
                if (clientTypeTokenNum.compareTo(securityBootProperties.getAllowUserClientTokenNum()) >= 0) {
                    int removeNum = (clientTypeTokenNum - securityBootProperties.getAllowUserClientNum()) + 1;
                    for (int i = 0; i < removeNum; i++) {
                        // 需要移除一个客户端
                        removeToken(securityAuthorityOfClientType.get(i).getToken());
                    }
                }
            }
        } else {
            // 如果客户端数为0 表示不限制最大数量
            if (securityBootProperties.getAllowUserClientNum().compareTo(0) != 0) {
                // 获取当前用户现有的客户端类型数量
                Integer clientTypeNum = securityAuthorityListOfUserGroupByClientType.keySet().size();
                // 判断是否现有的客户端数量已经达到上限，如果达到配置上线，则随机移除客户端
                if (clientTypeNum.compareTo(securityBootProperties.getAllowUserClientNum()) >= 0) {
                    int removeNum = (clientTypeNum - securityBootProperties.getAllowUserClientNum()) + 1;
                    for (int i = 0; i < removeNum; i++) {
                        // 如果当前的客户端数量已经等于了最多客户端数量,则需要顶掉之前的一个客户端
                        String removeClientType = getRandomElementUsingThreadLocalRandom(securityAuthorityListOfUserGroupByClientType.keySet());
                        if (StringUtils.isNotBlank(removeClientType)) {
                            // 移除掉任意客户端后，增加新的客户端token
                            securityAuthorityList = securityAuthorityList.stream().filter(ele -> !StringUtils.equals(ele.getClientType(), removeClientType)).collect(Collectors.toList());
                        }
                    }
                }
            }
        }
        // 保存这次token
        securityAuthorityList.add(0, securityAuthority);
    }

    @Override
    public void removeToken(String token) throws Exception {
        if (CollectionUtils.isEmpty(securityAuthorityList)) {
            return;
        }
        securityAuthorityList = securityAuthorityList.stream().filter(ele -> !StringUtils.equals(ele.getToken(), token)).collect(Collectors.toList());
    }

    @Override
    public Boolean checkToken(String token, String userId, String clientType) throws Exception {
        SecurityAuthority securityAuthority = securityAuthorityList.stream().filter(ele -> StringUtils.equals(ele.getToken(), token) && StringUtils.equals(ele.getUserId(), userId) && StringUtils.equals(ele.getClientType(), clientType)).findFirst().orElse(null);
        if (ObjectUtils.allNotNull(securityAuthority)) {
            Boolean isNotOverdue = checkSecurityAuthorityOverdue(securityAuthority);
            if (isNotOverdue) {
                return true;
            }
        }
        throw CommonException.create(ServerResponse.createByError(ResponseStatus.NO_AUTH.getCode(), "无效登录信息,请重新登录"));
    }

    @Override
    public SecurityAuthority getSecurityAuthorityByToken(String token) throws Exception {
        if (StringUtils.isBlank(token)) {
            throw CommonException.create(ServerResponse.createByError(Integer.valueOf(securityBootProperties.getNoAuthorizedCode()), "未上传用户token，请登录后重试", ""));
        }
        // 获取token 存在的有效权限信息
        SecurityAuthority securityAuthority = securityAuthorityList.stream().filter(ele -> StringUtils.equals(ele.getToken(), token)).findFirst().orElse(null);
        Boolean isNotOverdue = checkSecurityAuthorityOverdue(securityAuthority);
        if (isNotOverdue) {
            // 完成验证后 返回权限信息
            return securityAuthority;
        } else {
            throw CommonException.create(ServerResponse.createByError(ResponseStatus.NO_AUTH.getCode(), "登录信息已经失效,请重新登录"));
        }
    }

    @Override
    public void delayExpired(String token) throws Exception {
        //  使用cache中expireAfterAccess策略，则可以不用续期了，每次访问缓存会自动续期。
        SecurityAuthority securityAuthority = securityAuthorityList.stream().filter(ele -> StringUtils.equals(ele.getToken(), token)).findFirst().orElse(null);
        if (securityAuthority != null) {
            securityAuthority.setOverdueDate(DateUtils.addMinutes(new Date(), Integer.valueOf(securityBootProperties.getExpire())));
        }
    }


    // 判断是否已经过期 过期直接移除
    private Boolean checkSecurityAuthorityOverdue(SecurityAuthority securityAuthority) throws Exception {
        if (securityAuthority == null) {
            throw CommonException.create(ServerResponse.createByError(ResponseStatus.NO_AUTH.getCode(), "用户登录信息不存在"));
        }
        if (new Date().before(securityAuthority.getOverdueDate())) {
            return true;
        } else {
            removeToken(securityAuthority.getToken());
            return false;
        }
    }


    public String getRandomElementUsingThreadLocalRandom(Set<String> set) {
        if (set == null || set.isEmpty()) {
            return null;
        }

        List<String> list = new ArrayList<>(set);
        int randomIndex = ThreadLocalRandom.current().nextInt(list.size());
        return list.get(randomIndex);
    }
}
